package HierarchyPackage.AffixHierarchies;

import HierarchyPackage.*;
import ClassifiedDataSetPackage.ClassifiedDataSet;
import ClassifiedDataSetPackage.ClassifiedDataSetStorage;

import DataSetPackage.DataSetException;
import DataSetPackage.SimpleView;
import DataSetPackage.View;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import org.osdtsystem.utils.MemoryStorage;

/**
 * Hierarchy for classifying datapoints by their suffix.
 *
 * @author Martin Haulrich
 */
public class SuffixHierarchy extends AffixHierarchy implements HierarchyThreaded {

    /**
     * Constructor
     */
    public SuffixHierarchy() {
        super();
        setTop(new SuffixHClass(this, ""));

    }

    public synchronized SuffixHClass getHClass(String suf) {
        return new SuffixHClass(this, suf);
    }

    @Override
    public ClassifiedDataSet getClassifications(Collection<Integer> dids, View v, HClass topClass,
            int maxDepth, int minCount, int cutFraction, boolean saveData) {

        /**
         * @TODO minCount currently not in use
         */
        ClassifiedDataSetStorage result = new ClassifiedDataSetStorage(new MemoryStorage(), minCount, cutFraction);

        if ((topClass instanceof SuffixHClass) && (v instanceof SimpleView)) {

            SuffixHClass tC = (SuffixHClass) topClass;
            SimpleView dv = (SimpleView) v;

            Runnable r = new SuffixThread(dids, dv, tC, maxDepth, saveData, result);
            Thread t = new Thread(r);
            t.start();

            try {
                t.join();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }

        }
        return result;
    }

    @Override
    public ClassifiedDataSet getClassifications(Collection<Integer> dids, View v, HClass topClass,
            int maxDepth, int minCount, int cutFraction, boolean saveData, ExecutorService pool) {
        throw new UnsupportedOperationException("Threaded is no longer supported becuase we do not have the split operation any more - simply implemt slit on the collection");
//
//        /**
//         * @TODO minCount currently not in use
//         */
//        ClassifiedDataSetStorage result = new ClassifiedDataSetStorage(saveData);
//
//        if ((topClass instanceof SuffixHClass) && (v instanceof SimpleView)) {
//
//            SuffixHClass tC = (SuffixHClass) topClass;
//            SimpleView dv = (SimpleView) v;
//
//            int maxThreads = 16;
//            DataIDs[] dd = dids.Split(maxThreads);
//
//            Future[] ff = new Future[maxThreads];
//            Set<Integer> running = new HashSet<Integer>();
//
//            for (int i = 0; i < maxThreads; i++) {
//
//                ff[i] = pool.submit(new SuffixThread(dd[i], dv, tC, maxDepth, saveData, result));
//                running.add(i);
//            }
//
//            while (!running.isEmpty()) {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException ex) {
//                    ex.printStackTrace();
//                }
//                for (int i = 0; i < maxThreads; i++) {
//                    if (ff[i].isDone()) {
//                        running.remove(i);
//                    }
//                }
//            }
//
//        }
//        return result;
    }

    /**
     * Tread-object to use for threaded classification.
     */
    private class SuffixThread implements Runnable {

        /**
         * IDs for the data that this thread classifies.
         */
        Collection<Integer> dids;

        /**
         * View on the dataset.
         */
        SimpleView dv;

        /**
         * Class to classify with respect to.
         */
        SuffixHClass topClass;

        /**
         * Maximun depth of classifications.
         */
        int maxDepth;

        /**
         * Result of the classification.
         * This object is passed as a reference so all threads are writing to the same ClassifiedDataSetStorage.
         */
        ClassifiedDataSetStorage result;

        boolean saveData;

        /**
         * Constructor.
         * 
         * @param dids IDs for the data that this thread classifies.
         * @param dv View on the dataset.
         * @param topClass View on the dataset.
         * @param maxDepth Maximun depth of classifications.
         * @param result Result of the classification.
         * This object is passed as a reference so all threads are writing to the same ClasClassifiedDataSetMem
         */
        public SuffixThread(Collection<Integer> dids, SimpleView dv, SuffixHClass topClass, int maxDepth, boolean saveData, ClassifiedDataSetStorage result) {
            this.dids = dids;
            this.dv = dv;
            this.topClass = topClass;
            this.maxDepth = maxDepth;
            this.result = result;
            this.saveData = saveData;
        }

        /**
         * Initiates the classification.
         * The method that is run when the thread is started.
         */
        @Override
        public void run() {

            String topSuffix = topClass.getID();
            Map<HClass, List<Integer>> classification = new HashMap<HClass, List<Integer>>();
            Map<HClass, Integer> depts = new HashMap<HClass, Integer>();
            for (Iterator<Integer> it = dids.iterator(); it.hasNext();) {

                Integer did = it.next();
                String fea;

                try {


                    fea = dv.getFeature(did);

                    // Feature used here
                    String suffix = fea;

                    String curSuf = "";

                    int indexB = suffix.length() - topSuffix.length();


                    // Is suffix in fact classified under topSuffix
                    if (suffix.endsWith(topSuffix)) {
                        for (int d = 0; (d <= maxDepth) && (d <= indexB); d++) {

                            curSuf = suffix.subSequence(indexB - d, indexB) + topSuffix;
                            SuffixHClass clas = getHClass(curSuf);

                            List<Integer> classifiedByThisClass = classification.get(clas);
                            if (classifiedByThisClass == null) {
                                classifiedByThisClass = new ArrayList<Integer>();
                                classification.put(clas, classifiedByThisClass);
                            }
                            //    if (saveData) {
                            classifiedByThisClass.add(did);
                            //    }
                            depts.put(clas, d);
//                            result.addClassification(clas, d);
//                            result.addInstance(clas, did);
                        }
                    }
                } catch (DataSetException ex) {
                    ex.printStackTrace();
                }
            }
            for (Entry<HClass, List<Integer>> e : classification.entrySet()) {
//                result.addClassification(e.getKey(), depts.get(e.getKey()));
//                result.addInstances(e.getKey(), new HashSet<Long>(e.getValue()));
                result.addClassificationWithData(e.getKey(), depts.get(e.getKey()), e.getValue());
            }
        }
    }

    @Override
    public boolean superclassOf(HClass c1, HClass c2) {


        if (!((c1 instanceof SuffixHClass) && (c2 instanceof SuffixHClass))) {
            return false;
        }

        SuffixHClass sc1 = (SuffixHClass) c1;
        SuffixHClass sc2 = (SuffixHClass) c2;


        if (sc2.affix.endsWith(sc1.affix) && (sc2.affix.length() > sc1.affix.length())) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Get intersection between two classes in the hierarchy.
     * @param c1 First class in intersection.
     * @param c2 Second class in intersection.
     * @return Intersection of <code>c1</code> and <code>c2</code>.
     */
    @Override
    public HClass getIntersectionLocal(HClass c1, HClass c2) {


        //   String s1 = c1.getID();
        //   String s2 = c2.getID();
        String s1 = ((AffixHClass) c1).affix;
        String s2 = ((AffixHClass) c2).affix;
        //      System.err.println(s1 + " " + s2);

        // s2 is superclass to s1 in the hierarchy
        if (s1.endsWith(s2)) {
//            System.err.println("c1");
            return c1;
        } else {
            // s1 is superclass to s2 in the hierarchy
            if (s2.endsWith(s1)) {
                //             System.err.println("c2");
                return c2;
            } else {
                //             System.err.println("null");
                return null;
            }
        }

    }

    @Override
    public boolean isClassifiedBy(Integer did, View dv, HClass classification) {

        if (dv instanceof SimpleView && classification instanceof SuffixHClass) {


            SimpleView sdv = (SimpleView) dv;
            SuffixHClass sclas = (SuffixHClass) classification;

            String fea = sdv.getFeature(did);
            //String topSuf = sclas.getID();
            String topSuf = sclas.affix;

            /**
             * @TODO Figure out what the length condition is all about. Changed it from > to >= because "er" was not classified by suffix "er"
             */
            if (fea.endsWith(topSuf) && (fea.length() >= topSuf.length())) {
                return true;
            }
            return false;

        } else {
            throw new HierarchyException("Wrong view type. Should be a SimpleView - or classification is not in this a SuffixHclass");
        }
    }

    @Override
    public Map<HClass, Integer> saveEst(String filename, Set<HClass> classes) throws IOException {
        Integer id = 0;

        Map<HClass, Integer> result = new HashMap<HClass, Integer>();
        PrintWriter out = new PrintWriter(new FileWriter(filename));
        out.println("SuffixHierarchy");

        for (HClass c : classes) {
            SuffixHClass sc = (SuffixHClass) c;
            out.println(id + "\t" + sc.getID());
            result.put(c, id);
            id++;
        }


        out.close();
        return result;
    }

    @Override
    public Map<Integer, HClass> loadEst(String filename, Set<Integer> classIDs) throws IOException {

        Map<Integer, HClass> result = new HashMap<Integer, HClass>();
        top = new SuffixHClass(this, "");

        BufferedReader input = new BufferedReader(new FileReader(filename));
        String line;
        line = input.readLine();
        if (!line.equals("SuffixHierarchy")) {
            System.err.println("ERROR in first line");
        }


        while ((line = input.readLine()) != null) {
            String[] elements = line.split("\t");
            //  System.err.println(line);
            // If length == 1 the line contains top
            if (elements.length == 1) {
                result.put(Integer.valueOf(elements[0]), top);
            } else {
                result.put(Integer.valueOf(elements[0]), new SuffixHClass(this, elements[1]));
            }

        }
//        for (Entry<Long, HClass> e : result.entrySet()) {
//            try {
//                System.err.println("\t" + e.getKey() + " ; " + e.getValue().getName());
//            } catch (HierarchyException ex) {
//                Logger.getLogger(SuffixHierarchy.class.getName()).log(Level.SEVERE, null, ex);
//            }
//        }

        return result;

    }

    @Override
    public HClass newClass(Integer did, View v) {

        SimpleView sv = (SimpleView) v;
        /**
         * @TODO Again we are cheating. How dow we now that it is number 0 in the features list. Rethink the View-classes
         */
        String suffix = sv.getFeature(did);

        return new SuffixHClass(this, suffix);


    }

    @Override
    public String getType() {
        return "SuffixHierarchy";
    }
}
